package icasue.tools.sqlhelper.core;


import icasue.tools.sqlhelper.core.AbstractSqlProviderFrame;
import icasue.tools.sqlhelper.core.FrameParser;
import icasue.tools.sqlhelper.core.Consult;
import icasue.tools.sqlhelper.core.Queries;
import icasue.tools.sqlhelper.core.DBCol;
import icasue.tools.sqlhelper.core.Condition;
import icasue.tools.sqlhelper.core.SQLSplit;
import lombok.SneakyThrows;
import icasue.reflect.annotations.NotNull;
import icasue.tools.sqlhelper.common.Const;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.*;

import static icasue.tools.sqlhelper.common.Const.*;

class Update extends FrameParser {

    /**
     * 构建更新的SQL段.
     * @param inst 被执行更新的数据库实例.
     * @param chooseAble 是否插入段是可选择的(true: 属性有值才构建该段，false: 构建所有属性的段无论有没有值，空值使用null)
     * @param frame
     * @return
     * @throws IllegalAccessException
     */
    @SneakyThrows(Throwable.class)
    final static String _chooseUpdateByPrimary(@NotNull Object inst, boolean chooseAble, AbstractSqlProviderFrame frame) {
        // 持久化序列.
        final StringBuilder setMapSeq = new StringBuilder();
        // 更新条件序列.
        final StringBuilder conditionSeq = new StringBuilder();

        for (Map.Entry<String, DBCol> fieldMap : frame.dbTab.mappingRoot.entrySet()) {
            String dbField = fieldMap.getKey();
            DBCol dbCol = fieldMap.getValue();
            Field javaField = dbCol.field;
            Object val = javaField.get(inst);
            if(val != null){
                // "`col1` = 'val1',col2` = 'val2',"
                setMapSeq
                        .append($STRICT).append(dbField).append($STRICT).append($SPACE)
                        .append($EQ).append($SPACE)
                        .append($SEC).append(_formatVal(val)).append($SEC).append($DOT);
            }else {
                if(!chooseAble){
                    // "`col1` = 'val1',col2` = null,"
                    setMapSeq
                            .append($STRICT).append(dbField).append($STRICT).append($SPACE)
                            .append($EQ).append($SPACE)
                            .append(NULL).append($DOT);
                }
            }

            // "`primary1` = 'val1' and `primary2` = 'val2' and "
            if(dbCol.primary != null){
                if(val != null){
                    conditionSeq
                            .append($STRICT).append(dbField).append($STRICT).append($SPACE)
                            .append($EQ).append($SPACE)
                            .append($SEC).append(_formatVal(val)).append($SEC)
                            .append($SPACE).append(AND).append($SPACE);
                }else {
                    throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                            .concat(" column [" + dbField + "] declared primary but query is null value!"));
                }
            }
        }

        if(setMapSeq.length() == 0){
            throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                    .concat(" has no actually columns to update."));
        }else {
            setMapSeq.replace(setMapSeq.length() - 1,setMapSeq.length(),$EMPTY);
        }

        if(conditionSeq.length() == 0){
            throw new SQLException(frame.database().concat($SPOT).concat(frame.table())
                    .concat(" has not any primaryKey declared for update!"));
        }else {
            conditionSeq.replace(conditionSeq.length() - 5,conditionSeq.length(),$EMPTY);
        }

        String SQL = new StringBuilder()
                // "update "
                .append(UPDATE).append($SPACE)
                // "update `database`.`table` "
                .append(frame.database()).append($SPOT).append(frame.table()).append($SPACE)
                // "update `database`.`table` set [`col1` = '',`col2` = ''] "
                .append(SET).append($SPACE).append(setMapSeq.toString()).append($SPACE)
                // "update `database`.`table` set [`col1` = '',`col2` = ''] where [`id` = '']"
                .append(WHERE).append($SPACE).append(conditionSeq.toString())
                .toString();
        return SQL;
    }


    /**
     *
     * @param inst
     * @param consult
     * @param chooseAble
     * @param frame
     * @return
     */
    @SneakyThrows(Throwable.class)
    final static String _chooseUpdateByConsult(@NotNull Object inst, @NotNull Consult consult, boolean chooseAble, AbstractSqlProviderFrame frame) {
        // 查询条件序列.
        final StringBuilder conditionSeq = new StringBuilder();
        // JAVA属性到数据库列的映射.
        final Map<String, String> field_column = $fName_To_Column.apply(frame);

        /* 构建条件查询序列. */
        // 查询条件，对于复杂条件查询(包含 and，or，且存在查询组的) 仅做条件组织，不尝试进行优化
        // TODO: 这里将来是可以优化的地方，复杂度较高.

        // current query group's build buffer.
        final StringBuilder oneQueryGroup = new StringBuilder();
        // build all query groups.
        for (Queries query : (List<Queries>)consult.queries) {
            // create one query group, as format:
            // " and|or (col1 = 'val1',col2 like '%val2%')"
            // "(col1 = 'val1',col2 like '%val2%')"
            if(query.symbol != null){
                oneQueryGroup.append($SPACE).append(query.symbol).append($SPACE);
            }
            oneQueryGroup.append($BRACKETS_SMALL_LEFT);

            // build current query group's conditions.
            Boolean existInValidCondition = Boolean.FALSE;
            for (Condition condition : query.conditions) {
                if(condition.jField == null){
                    // condition build invoking onValid [.and() | .or()]
                    continue;
                }
                existInValidCondition = Boolean.TRUE;
                String column = field_column.get(condition.jField);
                boolean strictColumnMapping = true;
                if(column == null){
                    // JavaField not exist or is not a mapping field,
                    // perhaps user expect using "udf" as column to search, such as [length(geo_hash) > 6] !
                    // on this time, this query condition will retain.
                    column = condition.jField;
                    strictColumnMapping = false;
                }
                if(condition.logicRelationship != null){
                    oneQueryGroup.append($SPACE).append(condition.logicRelationship).append($SPACE);
                }
                SQLSplit sqlSplit = new SQLSplit(condition.operate, column, condition.values);
                oneQueryGroup.append(sqlSplit.getSqlSegment(strictColumnMapping));
            }
            oneQueryGroup.append($BRACKETS_SMALL_RIGHT).append($SPACE);

            // append this query group if exist inValid condition.
            if(existInValidCondition.booleanValue()){
                conditionSeq.append(oneQueryGroup.toString());
            }
            // clear current query group's build buffer.
            oneQueryGroup.replace(0, oneQueryGroup.length(), $EMPTY);
        }

        if(conditionSeq.length() == 0){
            conditionSeq.append(Const.SQL_CONDITION_TRUE);
        }else {
            conditionSeq.replace(conditionSeq.length() - 1, conditionSeq.length(), $EMPTY);
        }

        // 构建持久化列序列.
        StringBuilder setMapSeq = new StringBuilder();
        for (Map.Entry<String, DBCol> fieldMap : frame.dbTab.mappingRoot.entrySet()) {
            String dbField = fieldMap.getKey();
            DBCol dbCol = fieldMap.getValue();
            Field javaField = dbCol.field;
            Object val = javaField.get(inst);
            if(val != null){
                // "`col1` = 'val1',col2` = 'val2',"
                setMapSeq
                        .append($STRICT).append(dbField).append($STRICT).append($SPACE)
                        .append($EQ).append($SPACE)
                        .append($SEC).append(_formatVal(val)).append($SEC).append($DOT);
            }else {
                if(!chooseAble){
                    // "`col1` = 'val1',col2` = null,"
                    setMapSeq
                            .append($STRICT).append(dbField).append($STRICT).append($SPACE)
                            .append($EQ).append($SPACE)
                            .append(NULL).append($DOT);
                }
            }
        }

        if(setMapSeq.length() == 0){
            throw new SQLException("Has no actually column to update.");
        }else {
            setMapSeq.replace(setMapSeq.length() - 1,setMapSeq.length(), $EMPTY);
        }

        // sql build.
        String SQL = new StringBuilder()
                // "update "
                .append(UPDATE).append($SPACE)
                // "update `database`.`table` "
                .append(frame.database()).append($SPOT).append(frame.table()).append($SPACE)
                // "update `database`.`table` set [`col1` = '',`col2` = ''] "
                .append(SET).append($SPACE).append(setMapSeq.toString()).append($SPACE)
                // "update `database`.`table` set [`col1` = '',`col2` = ''] where [`id` = '']"
                .append(WHERE).append($SPACE).append(conditionSeq.toString())
                .toString();
        return SQL;
    }
}